package com.itheima.controller;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import com.itheima.entity.UserInfo;
import com.itheima.mapper.UserInfoMapper;
import com.itheima.utils.ResultUtils;

/**
 * @author ：yuan_he_song
 * @description: Redis数据结构应用测试
 * @date：2020/2/7 18:45
 */
@RestController
@RequestMapping("redis")
public class RedisTest {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private Redisson redisson;

    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * 1000 : 存入的键的个数
     * 0.001 误差率
     */
    BloomFilter<String> filter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("UTF-8")),1000,0.001);

    /**
     * 热点缓存key重建, 突发热点事件没有缓存预热,导致大量请求到数据库
     * 可以只允许一个线程查数据库写缓存, 其他线程走缓存, 微博热点劲爆事件解决示例
     * key :可以是消息或商品id
     */
    @GetMapping("gethot")
    public String getHotKey(String key)throws Exception{

        // 先用布隆过滤器判断是否存在, 缓存穿透的第一重拦截
        boolean isExist = filter.mightContain(key);
        if (!isExist){
            return "查询的key不存在";
        }


        // 从Redis缓存中获取
        String value = redisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(value)){
            // 只允许一个线程查数据库库写缓存 setnx命令分布式锁
            String teamKey = "lock:"+key;
            Boolean result = redisTemplate.opsForValue().setIfAbsent(teamKey, "1", 20, TimeUnit.SECONDS);
            if (result){
                 // 当前线程拿到分布式锁, 查询数据库放入缓存
                UserInfo userInfo = userInfoMapper.selectByPrimaryKey(Integer.parseInt(key));
                redisTemplate.boundValueOps(key).set(userInfo.getIdCard(),30,TimeUnit.MINUTES);
                redisTemplate.delete(teamKey);
            }
            else {  // 其他线程等到写缓存成功后再调方法
                Thread.sleep(50);
                getHotKey(key);
            }
        }

        // 缓存命中直接返回
        return value;
    }

    /**
     * 把热点数据放入布隆过滤器
     */
    public void putKey(List<String> ids){
        for (String key : ids) {
            filter.put(key);
        }

    }

    /**
     * 使用Redisson实现分布式锁
     * 解决当前锁失效后其他线程抢锁问题
     */
    @GetMapping("del_stock")
    public String delStock(){
        String lock = "productId_1001";
        RLock redissonLock = redisson.getLock(lock);
        try {
            // 加锁,内部线程为当前锁续命 默认30秒
            redissonLock.lock();
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0){
                stock = stock - 1;
                redisTemplate.opsForValue().set("stock",stock+"");
                System.out.println("剩余库存:" + stock);
            }
            else {
                System.out.println("库存不足");
            }

        }finally {
            redissonLock.unlock();
        }
        return "end";
    }

    /**
     * 分布式锁的实现
     * @return
     */
    @PostMapping("deuct_stock")
    public String deuctStock(){
        String lockKey = "product1001";
        String value = UUID.randomUUID().toString();
        try {
            // result 为true说明当前线程拿到货, redis中有lockKey这个key
            Boolean result = redisTemplate.boundValueOps(lockKey).setIfAbsent(value, 10, TimeUnit.SECONDS);
            if (!result){
                // 没拿到锁, 前端友好提示
                return "1001";
            }
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0){
                 stock = stock - 1;
                 redisTemplate.opsForValue().set("stock",stock+"");
                 System.out.println("剩余库存:" + stock);
            }
            else {
                System.out.println("库存不足");
            }

        }finally {
            // 释放锁 当前线程只能释放自己的线程锁
            if (value.equals(redisTemplate.boundValueOps(lockKey).get())){
                redisTemplate.delete(lockKey);
            }
        }

        return "end";
    }

    @GetMapping("look")
    public JSONObject look(Integer id, Long userId){
        /**
         * 点赞或抽奖讲用户id放入set集合
         */
        redisTemplate.opsForSet().add("activtyId",userId+"");
        return ResultUtils.successData(null,"success");
    }

    @GetMapping("get")
    public JSONObject get(String id){
        List list = Lists.newArrayList();
        // 模拟抽奖, 抽中的剔出抽箱子
        for (int i = 1; i < 4; i++) {
            Object choose = redisTemplate.boundSetOps(id).pop();
            list.add(choose);
        }
        Set members = redisTemplate.boundSetOps(id).members();
        return ResultUtils.successData(list,"success");
    }

}
